﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using ESpace;
using System.IO;
using System.Net;
using System.Net.Sockets;
using Ord;
using OrdonnanceAI;

namespace OrdonnanceServer
{
    public partial class Form1 : Form
    {
        // настройки для отладки алгоритмов ботов
        const bool bTest = false;
        const int logBotNum = 2;

        class Client
        {
            public EndPoint endPoint = null; // номер в списке клиентов (выдается и меняется в процессе)
            public String name; // является уникальным id клиента
            public String pass;
            public Client(String Name, String Pass)
            {
                name = Name;
                if (Name == "Администратор") pbAdmin = true;
                pass = Pass;
            }

            bool pbAdmin = false;
            public bool bAdmin { get { return pbAdmin; } }

            /// <summary> найти юзера в списке клиентов СЕРВЕРА </summary>
            public int GetInd(List<Socket> srvCl)
            {
                for (int i = 0; i < srvCl.Count; i++)
                    if (endPoint == srvCl[i].RemoteEndPoint) return i;
                return -1;
            }

            public void Send(byte[] buf)
            {
                sendL.Add(buf);
            }

            public bool isSend { get { return sendL.Count != 0; } }

            public List<byte[]> sendL = new List<byte[]>();

            /// <summary>счетчик после отправки последнего пакета. Для отправки проверочного пакета каждые 10сек</summary>
            public int cntSend = 0;

            /*public int RunSend(ETcpServer srv)
            {
                if (cntSend < 200) cntSend++;
                if (cntSend < 20) return 0;
                // если есть, что отправлять, отправляем и сбрасываем счетчик ожидания
                if (sendL.Count > 0)
                {
                    if (endPoint == null)
                    {
                        int nErr = sendL[0][0];
                        nErr *= -1;
                        sendL.Clear();
                        return nErr;
                    }

                    int n = sendL[0][0];
                    SendPack(srv, sendL[0]);
                    sendL.RemoveAt(0);
                    cntSend = 0;
                    return n;
                }
                // иначе если уже 10 секунд ничего небыло отправлено, отправляем проверочный пакет
                // и сбрасываем счетчик ожидания
                else if (cntSend == 200)
                {
                    byte[] buf = new byte[1] { 10 };
                    SendPack(srv, buf);
                    cntSend = 10;
                    return -1001;
                }
                return 0;
            }

            bool SendPack(ETcpServer srv, byte[] buf)
            {
                int ind = GetInd(srv.Clients);
                if (ind == -1) return false;
                if (srv.Send(buf, ind) != SendAns.Ok) return false;
                return true;
            }*/

            /// <summary> номер в списке клиентов cl по адресу в списке клиентов СЕРВЕРА</summary>
            public static int GetIdByEndPoint(List<Client> cl, EndPoint endPoint)
            {
                for (int i = 0; i < cl.Count; i++)
                    if (endPoint == cl[i].endPoint) return i;
                return -1;
            }

            /// <summary> номер в списке клиентов cl по имени юзера </summary>
            public static int GetIdByName(List<Client> cl, String Name)
            {
                for (int i = 0; i < cl.Count; i++)
                    if (Name == cl[i].name) return i;
                return -1;
            }

            public static bool DuplName(List<Client> cl, String Name)
            {
                for (int i = 0; i < cl.Count; i++)
                    if (Name == cl[i].name) return true;
                return false;
            }

        }

        List<Client> cl = new List<Client>();
        String fPath;

        class Game : Ord.Game
        {
            public List<Card> pack = new List<Card>(); // колода карт игры
            public int cntDelete = 0; // счетчик для удаления игры

            public DateTime dtBotPlayCard = DateTime.MinValue;
            public int NumBotwHoWaitPlayCard = -1;

            public Game(String Name, String Moderator)
                : base(Name, Moderator)
            {

            }

            public void NextPl()
            {
                PlayPl = NextPl(PlayPl);
            }
            Byte NextPl(Byte ActNum)
            {
                if (ActNum == 0) return (Byte)(ActNum + 1);
                // 2,3 игрока
                if (pl.Count <= 3)
                {
                    ActNum++;
                    if (ActNum >= pl.Count) ActNum -= (Byte)pl.Count;
                    return ActNum;
                }
                // 4 игрока
                if (pl.Count == 4)
                {
                    if (ActNum == 1) return 3;
                    if (ActNum == 3) return 2;
                    if (ActNum == 2) return 0;
                }
                // 5 игроков
                if (pl.Count == 5)
                {
                    if (ActNum == 1) return 3;
                    if (ActNum == 3) return 4;
                    if (ActNum == 4) return 2;
                    if (ActNum == 2) return 0;
                }
                // 6 игроков
                if (pl.Count == 6)
                {
                    if (ActNum == 1) return 3;
                    if (ActNum == 3) return 5;
                    if (ActNum == 5) return 4;
                    if (ActNum == 4) return 2;
                    if (ActNum == 2) return 0;
                }
                // 7 игроков
                if (pl.Count == 7)
                {
                    if (ActNum == 1) return 3;
                    if (ActNum == 3) return 5;
                    if (ActNum == 5) return 6;
                    if (ActNum == 6) return 4;
                    if (ActNum == 4) return 2;
                    if (ActNum == 2) return 0;
                }
                // 8 игроков
                if (pl.Count == 8)
                {
                    if (ActNum == 1) return 3;
                    if (ActNum == 3) return 5;
                    if (ActNum == 5) return 7;
                    if (ActNum == 7) return 6;
                    if (ActNum == 6) return 4;
                    if (ActNum == 4) return 2;
                    if (ActNum == 2) return 0;
                }
                return 0;
            }

        }

        List<Game> gmL = new List<Game>();

        List<String> Log = new List<String>();

        Bitmap[] bmpCard = new Bitmap[32];

        public Form1()
        {
            InitializeComponent();

            if (!Directory.Exists(Application.StartupPath + "\\Logs")) Directory.CreateDirectory(Application.StartupPath + "\\Logs");

            LoadConnectSettings(srv);
            if (bTest) AI.LogBotNum = logBotNum;

            if (!srv.Start())
            {
                MessageBox.Show("Не удалось запустить сервер");
                AddLog("Не удалось запустить сервер");
            }
            else
            {
                Text = "Ordonnance Server " + srv.LocalPoint;
            }
            fPath = Application.StartupPath + "\\ClientList.data";
            LoadClientList();
            RefillClientList();

            // создаем рисунки карт для отрисовки карт в руке
            for (int i = 1; i < 32; i++)
            {
                bmpCard[i] = new Bitmap(new Bitmap(Application.StartupPath + "\\Data\\" + i.ToString("00") + ".png"), new Size(96, 140));
            }
        }

        void LoadConnectSettings(ETcpServer serv)
        {
            String fName = "ConnectSettings.txt";
            if (!File.Exists(fName))
            {
                String[] strl = new String[2];
                strl[0] = "127.0.0.1 //IP-адрес сервера";
                strl[1] = "654 //порт сервера";
                File.WriteAllLines(fName, strl, Encoding.Default);
            }

            // загружаем настройки
            String[] sl = File.ReadAllLines(fName, Encoding.Default);
            if (sl.Length != 2) return;
            String[] sl1 = sl[0].Split(' ');
            if (sl1.Length < 1) return;
            String strIp = sl1[0];
            IPAddress ip = ETcp.GetAddr(strIp);
            if (ip == null) return;
            int p = 0;
            sl1 = sl[1].Split(' ');
            if (sl1.Length < 1) return;
            if (!int.TryParse(sl1[0], out p)) return;

            serv.LocalIP = strIp;
            serv.LocalPort = p;
        }

        void LoadClientList()
        {
            cl.Clear();
            // если файла нет, создаем его
            if (!File.Exists(fPath))
            {
                cl.Add(new Client("Администратор", "Хочу все знать!"));
                SaveClientList();
                return;
            }

            bool bAdmin = false;

            EStream st = new EStream(fPath);
            st.OpenLoad();

            int kolvo = st.LoadInt();
            for (int i = 0; i < kolvo; i++)
            {
                String n = st.LoadStr();
                String p = st.LoadStr();
                cl.Add(new Client(n, p));
                if (n == "Администратор") bAdmin = true;
            }
            st.CloseLoad();

            if (!bAdmin)
            {
                cl.Add(new Client("Администратор", "Хочу все знать!"));
                SaveClientList();
            }
        }

        void SaveClientList()
        {
            EStream st = new EStream(fPath);
            st.OpenSave();

            st.SaveInt(cl.Count);
            for (int i = 0; i < cl.Count; i++)
            {
                st.SaveStr(cl[i].name);
                st.SaveStr(cl[i].pass);
            }
            st.CloseSave();
        }

        void RefillClientList()
        {
            lbList.Items.Clear();
            for (int i = 0; i < cl.Count; i++)
            {
                String s = cl[i].name;
                if (cl[i].endPoint != null) s += " >> " + cl[i].endPoint.ToString();
                lbList.Items.Add(s);
            }
            // неизвестные подключения
            for (int i = 0; i < srv.Clients.Count; i++)
            {
                if (Client.GetIdByEndPoint(cl, srv.Clients[i].RemoteEndPoint) == -1)
                    lbList.Items.Add("Неизвестное подключение " + srv.Clients[i].RemoteEndPoint.ToString());
            }
            RefillGameList();
        }

        void RefillGameList()
        {
            tvGames.Nodes.Clear();

            for (int i = 0; i < gmL.Count; i++)
            {
                int idModer = Client.GetIdByName(cl, gmL[i].Moderator);
                String s = gmL[i].Name + " <<" + cl[idModer].name + ">>";
                if (cl[idModer].endPoint == null)
                {
                    s = gmL[i].Name + " " + cl[idModer].name;
                }
                // если игра играется
                if (gmL[i].GameIsPlay)
                {
                    s = "***" + s + "***";
                }
                TreeNode node = new TreeNode(s);
                node.Tag = gmL[i];
                tvGames.Nodes.Add(node);
                for (int j = 1; j < gmL[i].pl.Count; j++)
                {
                    s = " <<" + gmL[i].pl[j].Name + ">>";
                    if (!gmL[i].pl[j].isBot)
                    {
                        int id = Client.GetIdByName(cl, gmL[i].pl[j].Name);
                        if (cl[id].endPoint == null)
                        {
                            s = cl[id].name;
                        }
                    }
                    tvGames.Nodes[tvGames.Nodes.Count - 1].Nodes.Add(s);
                }
            }

            tvGames.ExpandAll();

            DrawBotCardsInGame();
        }

        string fPathLog = "";
        void AddLog(String s)
        {
            if (fPathLog == "") fPathLog = Application.StartupPath + "\\Logs\\Log start " + DateTime.Now.ToString("dd.MM.yyyy_HH.mm.ss") + ".log";
            string str = DateTime.Now.ToString("dd.MM.yy HH:mm:ss,fff") + " >> " + s;
            Log.Add(str);
            File.AppendAllText(fPathLog, str + "\r\n", Encoding.UTF8);
            if (Log.Count > 500) Log.RemoveAt(0);
            RefillLog();
        }

        void AddErr(String s)
        {
            AddLog("ОШИБКА:" + s);
        }

        void RefillLog()
        {
            if (chbxVisLog.Checked)
            {
                StringBuilder str = new StringBuilder();
                int StartI = Log.Count - 100; if (StartI < 0) StartI = 0;
                for (int i = StartI; i < Log.Count; i++)
                    str.AppendLine(Log[i]);
                txbLog.Text = str.ToString();
                txbLog.Select(txbLog.Text.Length, 0);
                txbLog.ScrollToCaret();
            }
            else txbLog.Text = "";
        }

        private void srv_OnConnect(object Sender, ESpace.ETcpServer.ConnectEventArgs e)
        {
            AddLog("Подключился новый юзер");
            RefillClientList();
        }

        private void srv_OnDisconnect(object Sender, ESpace.ETcpServer.DisconnectEventArgs e)
        {
            int id = Client.GetIdByEndPoint(cl, e.endPoint);

            if (e.notConnect)
            {
                if (id == -1)
                    AddLog("Разорвана связь с клиентом " + e.endPoint.ToString());
                else
                    AddLog("Разорвана связь с клиентом " + cl[id].name);
            }
            else
            {
                if (id == -1)
                    AddLog("Отключился клиент " + e.endPoint.ToString());
                else
                    AddLog("Отключился клиент " + cl[id].name);
            }

            // дисконектим пользователя в списке
            if (id != -1)
            {
                // дисконектим пользователя в списке
                cl[id].endPoint = null;
                // отправляем остальным юзерам обновленный список
                if (!cl[id].bAdmin) SendClientList();
            }
            RefillClientList();
        }

        private void srv_OnRecive(object Sender, ESpace.ETcpServer.RecvEventArgs e)
        {
            // смотрим юзера
            int idCl = Client.GetIdByEndPoint(cl, srv.Clients[e.indClient].RemoteEndPoint);
            String clAddr = srv.Clients[e.indClient].RemoteEndPoint.ToString();


            if (e.buf.Length < 1)
            {
                if (idCl == -1) AddErr("принят буфер нулевой длины от " + clAddr);
                else AddErr("принят буфер нулевой длины от " + cl[idCl].name);
                return;
            }

            /*if (e.buf.Length < 2 && e.buf[0] != 10 && e.buf[0] != 11 && e.buf[0] != 12 && e.buf[0] != 13 && e.buf[0] != 26 && e.buf[0] != 51 && e.buf[0] != 3)
            { 
                if (idCl == -1) AddErr("принят буфер единичной длины от " + clAddr);
                else AddErr("принят буфер единичной длины от " + cl[idCl].name);
                return;
            }*/

            byte idPack = e.buf[0];

            if (idCl == -1) AddLog("Принят пакет от " + clAddr + " idPack=" + idPack.ToString());
            else AddLog("Принят пакет от " + cl[idCl].name + " idPack=" + idPack.ToString());

            // логин нового юзера
            if (idPack == 1)
                LogIn(e.buf, e.indClient);
            // регистрация нового юзера
            if (idPack == 2)
                Registration(e.buf, e.indClient);
            // обновление
            if (idPack == 255)
                UpDateVersion(e.buf);
            // ответ, что новая версия загружена
            if (idPack == 254)
                AnsverNewVertion(e.indClient);

            // если пришел недопустимый пакет от неизвестного юзера 
            if (idCl == -1 && idPack > 2 && idPack < 254)
            {
                //srv.Send(new byte[1] { 200 }, e.indClient); //отправляем ему пакет-ошибку
                return;
            }

            // сообщение в общий чат
            if (idPack == 5)
                SendClientMess(e.buf);
            // смена пароля
            if (idPack == 6)
                ChangePass(e.buf, idCl);

            #region *** админские *****************

            if (idPack == 11) Adm_SendClientList(idCl); // обновить список клиентов
            if (idPack == 12) Adm_SendGameList(idCl); // обновить список игр
            if (idPack == 13) Adm_SendLog(idCl); // обновить лог
            if (idPack == 14) Adm_DelUser(e.buf, idCl); // удалить юзера
            if (idPack == 15) Adm_DelGame(e.buf, idCl); // удалить игру
            if (idPack == 16) Adm_ChangePass(e.buf, idCl); // сменить пароль юзеру
            if (idPack == 17) Adm_KickClient(e.buf, idCl); // отключить клиента

            #endregion *** админские *****************

            // добавить игру
            if (idPack == 20)
                AddGame(e.buf, idCl);
            // удалить игру
            if (idPack == 21)
                DeleteGame(e.buf, idCl);
            // удалить юзера из игры
            if (idPack == 22)
                DeleteUserInGame(e.buf, idCl, 22);
            // добавить заявку на участие в игре
            if (idPack == 23)
                AddIntoGame(e.buf, idCl);
            // удалить заявку на участие в игре
            if (idPack == 24)
                DeleteUserInGame(e.buf, idCl, 24);
            // начать игру
            if (idPack == 25)
                BeginGame(e.buf, idCl);
            // обновить список игр
            if (idPack == 26)
                SendGameList(idCl);
            // добавить бота
            if (idPack == 27)
                AddBot(e.buf, idCl);
            // положить карту на стол
            if (idPack == 50)
                PlayCardInTable(e.buf, idCl);
            // обновить стол
            if (idPack == 51)
                SendTable(e.buf, idCl);

        }

        #region *** Для админа *********************************************

        // отправка списока подключеных юзеров (11)
        void Adm_SendClientList(int idCl)
        {
            // проверка админских прав
            if (!cl[idCl].bAdmin)
            {
                SendAnsv(11, 10, null, idCl);
                AddErr("Пришел пакет с администраторским запросом от клиента " + cl[idCl].name + " но он не опознан как Администратор");
                return;
            }

            BufWriter bw = new BufWriter();
            bw.WriteByte(11);
            // количество
            bw.AddBufWriterMas(Adm_GetClientList());
            AddSend(idCl, bw.buf);
            AddLog("Отправлено сообщение админу (11) со списком подключенных клиентов");
        }

        byte[] Adm_GetClientList()
        {
            BufWriter bw = new BufWriter();
            try
            {
                #region *** клиенты ****************

                // количество
                bw.WriteInt(cl.Count);
                // пробегаемся по всем клиентам
                for (int i = 0; i < cl.Count; i++)
                {
                    bw.WriteString(cl[i].name);  // пишем имя
                    bw.WriteString(cl[i].pass);  // пишем пароль

                    if (cl[i].endPoint != null)
                        bw.WriteString(cl[i].endPoint.ToString()); // пишем адрес
                    else
                        bw.WriteString(""); // пишем адрес
                }

                #endregion *** клиенты ****************

                #region *** неизвестные подключения ******
                // количество
                int kolVoUnknown = 0;
                for (int i = 0; i < srv.Clients.Count; i++)
                {
                    int idC = Client.GetIdByEndPoint(cl, srv.Clients[i].RemoteEndPoint);
                    if (idC == -1) kolVoUnknown++;
                }
                bw.WriteInt(kolVoUnknown);
                for (int i = 0; i < srv.Clients.Count; i++)
                {
                    int idC = Client.GetIdByEndPoint(cl, srv.Clients[i].RemoteEndPoint);
                    if (idC == -1)
                    {
                        bw.WriteString(srv.Clients[i].RemoteEndPoint.ToString()); // пишем адрес
                    }
                }

                #endregion *** неизвестные подключения ******

            }
            catch (Exception ex) { AddErr("фия Adm_GetClientList: " + ex.Message); }
            return bw.buf;
        }

        // отправка списока игр (12)
        void Adm_SendGameList(int idCl)
        {
            // проверка админских прав
            if (!cl[idCl].bAdmin)
            {
                SendAnsv(12, 10, null, idCl);
                AddErr("Пришел пакет с администраторским запросом от клиента " + cl[idCl].name + " но он не опознан как Администратор");
                return;
            }

            // отправляем всем обновленный список
            BufWriter bw = new BufWriter();
            bw.WriteByte(12);
            bw.AddBufWriterMas(Adm_GetMessGameList());
            // отправляем пакет
            AddSend(idCl, bw.buf);
            AddLog("Отправлено сообщение админу (12) со списком игр");
        }

        byte[] Adm_GetMessGameList()
        {
            BufWriter bw = new BufWriter();
            try
            {
                bw.WriteInt(gmL.Count); // количество игр
                for (int i = 0; i < gmL.Count; i++)
                {
                    bw.WriteString(gmL[i].Name); // имя игры
                    bw.WriteInt(gmL[i].pl.Count); // количество игроков в игре
                    // статус игры (запущена, нет)
                    if (gmL[i].GameIsPlay) bw.WriteByte(1);
                    else bw.WriteByte(0);
                    // список игроков
                    for (int j = 0; j < gmL[i].pl.Count; j++)
                    {
                        bw.WriteString(gmL[i].pl[j].Name);
                        // бот или нет
                        if (gmL[i].pl[j].isBot) bw.WriteByte(1);
                        else bw.WriteByte(0);
                    }
                }

            }
            catch (Exception ex) { AddErr("Adm_GetMessGameList: " + ex.Message); }
            return bw.buf;
        }

        // отправка лога (13)
        void Adm_SendLog(int idCl)
        {
            // проверка админских прав
            if (!cl[idCl].bAdmin)
            {
                SendAnsv(13, 10, null, idCl);
                AddErr("Пришел пакет с администраторским запросом от клиента " + cl[idCl].name + " но он не опознан как Администратор");
                return;
            }

            // отправляем всем обновленный список
            BufWriter bw = new BufWriter();

            try
            {
                bw.WriteByte(13);
                StringBuilder s = new StringBuilder();
                for (int i = Log.Count - 1; i >= 0; i--)
                {
                    if (s.Length + Log[i].Length > 5000) break;
                    s.Insert(0, Log[i] + "\r\n");
                }
                bw.WriteString(s.ToString());
            }
            catch (Exception ex) { AddErr("ошибка создания пакета лога для администратора: " + ex.Message); }
            // отправляем пакет
            AddSend(idCl, bw.buf);
            AddLog("Отправлено сообщение админу (12) со логом сервера");
        }

        // удалить юзера (14)
        void Adm_DelUser(byte[] buf, int idCl)
        {
            // проверка админских прав
            if (!cl[idCl].bAdmin)
            {
                SendAnsv(14, 10, null, idCl);
                AddErr("Пришел пакет с администраторским запросом от клиента " + cl[idCl].name + " но он не опознан как Администратор");
                return;
            }

            try
            {
                // смотрим имя юзера
                BufReader br = new BufReader(buf);
                br.ReadByte();
                int idClUs = Client.GetIdByName(cl, br.ReadString());
                // был ли найден юзер
                if (idClUs == -1)
                {
                    SendAnsv(14, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение админу (14) не найден юзер для удаления");
                    return;
                }

                // если клиент подключен, дисконнектим его
                int ind = cl[idClUs].GetInd(srv.Clients);
                if (ind != -1)
                {
                    AddLog("Дисконнектим юзера " + cl[idClUs].name);
                    srv.DisconnectClient(ind);
                }
                AddLog("Удаляем юзера " + cl[idClUs].name);
                cl.RemoveAt(idClUs);
                RefillClientList();
                SaveClientList();

                AddLog("Отправляем ссобщение админу (14) с обновленным списком");
                SendAnsv(14, 0, Adm_GetClientList(), idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (14) удаление юзера админом: " + ex.Message); }
        }

        // удалить игру (15)
        void Adm_DelGame(byte[] buf, int idCl)
        {
            // проверка админских прав
            if (!cl[idCl].bAdmin)
            {
                SendAnsv(15, 10, null, idCl);
                AddErr("Пришел пакет с администраторским запросом от клиента " + cl[idCl].name + " но он не опознан как Администратор");
                return;
            }

            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte();

                // смотрим id игры
                int idGame = GetGameIdByName(br.ReadString());
                if (idGame == -1)
                {
                    SendAnsv(15, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение админу (15) не найдена игра");
                    return;
                }

                // удаляем игру
                gmL.RemoveAt(idGame);
                RefillGameList();

                // отправляем ответ
                SendAnsv(15, 0, Adm_GetMessGameList(), idCl);

                // посылаем всем обновленный список игр остальным юзерам
                SendGameListToAllUsers(idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (15) удаление игры админом: " + ex.Message); }
        }

        // сменить пароль юзеру (16)
        void Adm_ChangePass(byte[] buf, int idCl)
        {
            // проверка админских прав
            if (!cl[idCl].bAdmin)
            {
                SendAnsv(16, 10, null, idCl);
                AddErr("Пришел пакет с администраторским запросом от клиента " + cl[idCl].name + " но он не опознан как Администратор");
                return;
            }

            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte();

                // смотрим имя юзера
                int idClUs = Client.GetIdByName(cl, br.ReadString());
                if (idClUs == -1)
                {
                    SendAnsv(16, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение админу (16) не найден юзер");
                    return;
                }

                // смотрим новый пароль
                String newPass = br.ReadString();
                if (newPass == "")
                {
                    SendAnsv(16, 2, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение админу (16) новый пароль пустой");
                    return;
                }

                // меняем пароль
                cl[idClUs].pass = newPass;

                // сохраняем
                SaveClientList();

                // если клиент залогинен, отправляем ему новый пароль
                if (cl[idClUs].endPoint != null)
                {
                    SendAnsv(6, 0, null, idClUs);
                }

                SendAnsv(16, 0, Adm_GetClientList(), idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (16) смена пароля админом: " + ex.Message); }
        }

        // отключить клиента (17)
        void Adm_KickClient(byte[] buf, int idCl)
        {
            // проверка админских прав
            if (!cl[idCl].bAdmin)
            {
                SendAnsv(17, 10, null, idCl);
                AddErr("Пришел пакет с администраторским запросом от клиента " + cl[idCl].name + " но он не опознан как Администратор");
                return;
            }

            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte();

                // смотрим адрес юзера
                String addr = br.ReadString();
                int idClSrv = -1;
                for (int i = 0; i < srv.Clients.Count; i++)
                    if (addr == srv.Clients[i].RemoteEndPoint.ToString())
                    {
                        idClSrv = i;
                        break;
                    }

                if (idClSrv == -1)
                {
                    SendAnsv(17, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение админу (17) не найден клиент в списке подключенных к серверу клиентов");
                    return;
                }

                // смотрим имя юзера (если это залогиненый юзер)
                int idClUs = Client.GetIdByEndPoint(cl, srv.Clients[idClSrv].RemoteEndPoint);

                // отключаем юзера
                srv.DisconnectClient(idClSrv);

                // отправлем ответ
                SendAnsv(17, 0, Adm_GetClientList(), idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (17) отключение юзера админом: " + ex.Message); }
        }

        #endregion *** Для админа *********************************************

        /// <summary>Возводит ожидание, по окончании которого бот сходит. Ожидание для того, чтобы боты не ходили мгновенно</summary>
        void SetBotPlay(Game game, int indPlayer)
        {
            game.dtBotPlayCard = DateTime.Now.AddSeconds(1);
            game.NumBotwHoWaitPlayCard = indPlayer;
        }

        void AddSend(int idCl, byte[] buf)
        {
            if (cl[idCl].endPoint == null) return;
            cl[idCl].Send(buf);
        }

        #region *** посылка обновлений ******************************************

        // оправить юзеру список обновленных игр (26)
        void SendGameList(int idCl)
        {
            // отправляем всем обновленный список
            BufWriter bw = new BufWriter();
            bw.WriteByte(26);
            bw.AddBufWriterMas(GetMessGameList(cl[idCl].name));
            // отправляем пакет
            AddLog("Добавляем в очередь отправки юзеру " + cl[idCl].name + " список игр");
            AddSend(idCl, bw.buf);
        }

        /// <summary>отправить юзеру список доступных ботов (28)</summary>
        void SendBotList(int idCl)
        {
            BufWriter bw = new BufWriter();
            bw.WriteByte(28);
            Dictionary<byte, string> bots = AI.GetAvailableBots();
            bw.WriteInt(bots.Count);
            foreach (KeyValuePair<byte, string> bot in bots)
            {
                bw.WriteByte(bot.Key);
                bw.WriteString(bot.Value);
            }
            // отправляем пакет
            AddLog("Добавляем в очередь отправки юзеру " + cl[idCl].name + " список доступных ботов");
            AddSend(idCl, bw.buf);
        }

        // оправить всем юзерам список обновленных игр (26)
        void SendGameListToAllUsers(int idClNotSet)
        {
            if (idClNotSet >= 0 && idClNotSet < cl.Count)
                AddLog("Добавляем в очередь отправки всем юзерам, кроме " + cl[idClNotSet].name + " список игр ");
            else
                AddLog("Добавляем в очередь отправки всем юзерам пакет  список игр ");

            // отправляем всем обновленный список
            // отправляем всем пакет (кроме исключенного клиента)
            for (int i = 0; i < cl.Count; i++)
            {
                BufWriter bw = new BufWriter();
                bw.WriteByte(26);
                bw.AddBufWriterMas(GetMessGameList(cl[i].name));
                if (i != idClNotSet)
                    AddSend(i, bw.buf);
            }
        }

        // (51)
        void SendTable(byte[] buf, int idCl)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte();
                String gmName = br.ReadString(); // смотрим имя игры
                // если такого имени нет, отправляем ошибку
                if (gmName == "")
                {
                    SendAnsv(51, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (51) имя игры пустое");
                    return;
                }

                // находим игру
                int idGame = GetGameIdByName(gmName);
                if (idGame == -1)
                {
                    SendAnsv(51, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (51) не найдена игра в списке");
                    return;
                }

                // смотрим игрока с списке юзеров игры
                int idUser = GetUserInGameIdByName(idGame, cl[idCl].name);
                if (idGame == -1)
                {
                    SendAnsv(51, 2, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (51) не найден юзер в списке игроков игры");
                    return;
                }

                AddLog("Отправляем стол (51)");
                SendAnsv(51, 0, GetMessUpdateTable(idGame, idUser), idCl);
            }
            catch (Exception ex) { AddErr("ошибка разбора пакета (51) обновить стол: " + ex.Message); }
        }

        byte[] GetMessGameList(String userName)
        {
            BufWriter bw = new BufWriter();
            try
            {
                // считаем количество игр
                // если игра запущена и игрока в ней нет, не отправляем ему эту игру
                int kolvo = 0;
                for (int i = 0; i < gmL.Count; i++)
                    if (!gmL[i].GameIsPlay || gmL[i].GetPlayarIndByName(userName) != -1)
                        kolvo++;

                bw.WriteInt(kolvo); // количество игр
                for (int i = 0; i < gmL.Count; i++)
                {
                    // если игра запущена и игрока в ней нет, не отправляем ему эту игру
                    if (gmL[i].GameIsPlay && gmL[i].GetPlayarIndByName(userName) == -1) continue;

                    bw.WriteString(gmL[i].Name); // имя игры
                    bw.WriteInt(gmL[i].pl.Count); // количество игроков в игре

                    bw.WriteByte((byte)(gmL[i].GameIsPlay ? 1 : 0));// статус игры (запущена, нет)
                    bw.WriteString(gmL[i].Moderator);// модератор игры
                    // список игроков
                    for (int j = 0; j < gmL[i].pl.Count; j++)
                    {
                        bw.WriteString(gmL[i].pl[j].Name);
                        // бот или нет
                        if (gmL[i].pl[j].isBot) bw.WriteByte(1);
                        else bw.WriteByte(0);
                    }
                }
            }
            catch (Exception ex) { AddErr("ошибка GetMessGameList: " + ex.Message); }
            return bw.buf;
        }

        byte[] GetMessCardInHand(List<Card> crd)
        {
            BufWriter bw = new BufWriter();
            try
            {
                // карты в руке
                bw.WriteByte((byte)crd.Count);
                // формируем непрерывный массив
                List<bool> bf = new List<bool>();
                for (int i = 0; i < crd.Count; i++)
                {
                    for (int j = 0; j < 5; j++)
                    {
                        bf.Add(crd[i].troop[j]);
                    }
                }

                bw.WriteBitMas(bf.ToArray());

            }
            catch (Exception ex) { AddErr("Ошибка GetMessCardInHand: " + ex.Message); }
            return bw.buf;
        }

        byte[] GetMessUpdateTable(int GameId, int idUserInGameList)
        {
            return GetMessUpdateTable(gmL[GameId], idUserInGameList);
        }

        byte[] GetMessUpdateTable(Game game, int idUserInGameList)
        {
            DateTime dt = DateTime.Now;
            BufWriter bw = new BufWriter();
            bw.WriteString(game.Name);
            bw.AddBufWriterMas(GetMessCardInHand(game.pl[idUserInGameList].card));
            bw.AddBufWriterMas(GetMessTable(game.pl, game.PlayPl));
            return bw.buf;
        }

        byte[] GetMessTable(List<Player> pl, int ActPl)
        {
            BufWriter bw = new BufWriter();
            try
            {
                // карты на столе
                bw.WriteByte((byte)pl.Count);

                // пишем количество карт в каждом городе каждого игрока
                // и одновременно формируем непрерывный массив
                List<bool> bf = new List<bool>();
                for (int p = 0; p < pl.Count; p++)
                {
                    for (int t = 0; t < 5; t++)
                    {
                        bw.WriteByte((byte)pl[p].town[t].card.Count);
                        for (int c = 0; c < pl[p].town[t].card.Count; c++)
                        {
                            // формируем непрерывный массив
                            for (int tr = 0; tr < 5; tr++)
                                bf.Add(pl[p].town[t].card[c].troop[tr]);
                        }
                    }

                }

                // записываем массив
                bw.WriteBitMas(bf.ToArray());

                // последняя карта каждого игрока
                for (int p = 0; p < pl.Count; p++)
                {
                    bw.WriteInt(pl[p].LastCard.X);
                    bw.WriteInt(pl[p].LastCard.Y);
                }

                // текущий игрок
                bw.WriteByte((byte)ActPl);



            }
            catch (Exception ex) { AddErr("Ошибка GetMessTable: " + ex.Message); }
            return bw.buf;
        }

        // отправка всем юзерам список подключеных юзеров (3)
        void SendClientList()
        {
            BufWriter bw = new BufWriter();
            bw.WriteByte(3);

            // считаем подключеных юзеров
            int sz = 0;
            for (int i = 0; i < cl.Count; i++)
                if (cl[i].endPoint != null && !cl[i].bAdmin) sz++;
            bw.WriteInt(sz);
            // пробегаемся по всем юзерам
            for (int i = 0; i < cl.Count; i++)
            {
                // если юзер подключен
                if (cl[i].endPoint != null && !cl[i].bAdmin)
                {
                    // формируем пакет
                    bw.WriteString(cl[i].name);
                }
            }

            SendPackToAllUsers(bw.buf, -1);
        }

        #endregion *** посылка обновлений ******************************************

        #region *** посылка ответов на запросы ******************************************

        // ответ - добавление игры (20)
        void AddGame(byte[] buf, int idCl)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte(); // id - запроса
                String s = br.ReadString(); // имя игры
                // смотрим, не пустое ли имя
                if (s == "")
                {
                    SendAnsv(20, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (20) имя игры пустое");
                    return;
                }

                // смотрим, не повторяется ли имя игры
                if (GetGameIdByName(s) != -1)
                {
                    SendAnsv(20, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (20) такая игра уже есть в списке игр");
                    return;
                }

                // добавляем игру
                Game game = new Game(s, cl[idCl].name);
                game.pl.Add(new Player(game.Moderator));
                gmL.Add(game);
                RefillGameList();

                // отправляем ответ
                SendAnsv(20, 0, GetMessGameList(cl[idCl].name), idCl);

                // посылаем обновленный список игр остальным юзерам
                SendGameListToAllUsers(idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (20) добавление игры: " + ex.Message); }
        }

        // ответ - удаление игры (21)
        void DeleteGame(byte[] buf, int idCl)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte(); // id - запроса
                String s = br.ReadString(); // имя игры
                // смотрим, не пустое ли имя
                if (s == "")
                {
                    SendAnsv(21, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (21) имя игры пустое");
                    return;
                }

                // смотрим, существует ли такая игра
                int GameId = GetGameIdByName(s);
                if (GameId == -1)
                {
                    SendAnsv(21, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (21) игра уже не существует");
                    return;
                }

                // удаляем игру
                gmL.RemoveAt(GameId);
                RefillGameList();

                // отправляем ответ
                SendAnsv(21, 0, GetMessGameList(cl[idCl].name), idCl);

                // посылаем всем обновленный список игр остальным юзерам
                SendGameListToAllUsers(idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (21) удаление игры: " + ex.Message); }
        }

        // ответ - удаление пользователя из игры (22) (24)
        void DeleteUserInGame(byte[] buf, int idCl, byte idPack)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte(); // id - запроса
                String s = br.ReadString(); // имя игры
                String s1 = br.ReadString(); // имя юзера
                // смотрим, не пустое ли имя
                if (s == "" || s1 == "")
                {
                    SendAnsv(idPack, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (22,24) имя пользователя пустое");
                    return;
                }

                // смотрим, существует ли такая игра
                int GameId = GetGameIdByName(s);
                if (GameId == -1)
                {
                    SendAnsv(idPack, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (22,24) не найдено имя игры");
                    return;
                }

                // смотрим, существует ли такой юзер в игре
                int UserId = GetUserInGameIdByName(GameId, s1);
                if (UserId == -1)
                {
                    SendAnsv(idPack, 2, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (22,24) такого игрока нет в этой игре");
                    return;
                }

                // удаляем юзера из игры
                String DelUserName = gmL[GameId].pl[UserId].Name;
                gmL[GameId].pl.RemoveAt(UserId);
                RefillGameList();

                // отправляем ответ
                SendAnsv(idPack, 0, GetMessGameList(cl[idCl].name), idCl);

                // посылаем бновленный список игр остальным юзерам
                SendGameListToAllUsers(idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (22)(24) удаление пользователя из игры: " + ex.Message); }
        }

        // ответ - заявка на участие в игре (23)
        void AddIntoGame(byte[] buf, int idCl)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte(); // id - запроса
                String s = br.ReadString(); // имя игры
                // смотрим, не пустое ли имя
                if (s == "")
                {
                    SendAnsv(23, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (23) имя игрока пустое");
                    return;
                }

                // смотрим, существует ли такая игра
                int GameId = GetGameIdByName(s);
                if (GameId == -1)
                {
                    SendAnsv(23, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (23) не найдена игра в списке");
                    return;
                }

                // смотрим, существует ли уже игрок в игре
                if (GetUserInGameIdByName(GameId, cl[idCl].name) != -1)
                {
                    SendAnsv(23, 2, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (23) такой игрок уже в игре");
                    return;
                }

                // смотрим, можно ли еще принимать в игру игроков
                if (gmL[GameId].pl.Count >= 6)
                {
                    SendAnsv(23, 3, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (23) в игре уже максимальное количество игроков");
                    return;
                }

                AddLog("Добавляем игрока " + cl[idCl].name + " в игру");

                // добавляем игрока в игру
                gmL[GameId].pl.Add(new Player(cl[idCl].name));
                RefillGameList();

                // отправляем ответ
                SendAnsv(23, 0, GetMessGameList(cl[idCl].name), idCl);

                // посылаем обновленный список игр остальным юзерам
                SendGameListToAllUsers(idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (23) добавление игрока в игру: " + ex.Message); }

        }

        // ответ - начать игру (25)
        void BeginGame(byte[] buf, int idCl)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte(); // id - запроса
                String s = br.ReadString(); // имя игры
                // смотрим, не пустое ли имя
                if (s == "")
                {
                    SendAnsv(25, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (25) имя игры пустое");
                    return;
                }

                // смотрим, существует ли такая игра
                int GameId = GetGameIdByName(s);
                if (GameId == -1)
                {
                    SendAnsv(25, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (25) не найдено имя игры");
                    return;
                }

                // смотрим, все ли online
                for (int i = 0; i < gmL[GameId].pl.Count; i++)
                {
                    int idUser = Client.GetIdByName(cl, gmL[GameId].pl[i].Name);
                    if (idUser != -1)
                    {
                        if (cl[idUser].endPoint == null)
                        {
                            SendAnsv(25, 2, null, idCl);
                            AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (25) не все игроки online");
                            return;
                        }
                    }
                }

                // смотрим, количество человек
                if (gmL[GameId].pl.Count < 2)
                {
                    SendAnsv(25, 3, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (25) игроков должно быть минимум 2");
                    return;
                }

                // создаем колоду
                if (gmL[GameId].pl.Count < 3) gmL[GameId].pack = Gl.CreatePack(2, 1, 1, 2, 3);
                else if (gmL[GameId].pl.Count < 5) gmL[GameId].pack = Gl.CreatePack(2, 2, 2, 3, 6);
                else gmL[GameId].pack = Gl.CreatePack(4, 3, 3, 5, 9);
                // перемешиваем
                Gl.Shuffle(gmL[GameId].pack);
                // раздаем
                Gl.DealCards(gmL[GameId].pl, gmL[GameId].pack);

                //находим игрока в списке игры
                int idUs = GetUserInGameIdByName(GameId, cl[idCl].name);

                // говорим, что игра запущена
                gmL[GameId].GameIsPlay = true;

                // отправляем юзеру, вступление в игру (+ обновленый список игр)
                SendAnsv(25, 0, GetMessGameList(cl[idCl].name), idCl);

                // отправляем всем клиентам, обновленый список игр
                SendGameListToAllUsers(idCl);

                RefillGameList();
            }
            catch (Exception ex) { AddErr("ошибка ответа (25) начать игру: " + ex.Message); }

        }

        // ответ - добавить бота (27) 
        void AddBot(byte[] buf, int idCl)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte(); // id - запроса
                String s = br.ReadString(); // имя игры
                // смотрим, не пустое ли имя
                if (s == "")
                {
                    SendAnsv(27, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (27) имя игры пустое");
                    return;
                }

                // смотрим, существует ли такая игра
                int GameId = GetGameIdByName(s);
                if (GameId == -1)
                {
                    SendAnsv(27, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (27) имя игры не найдено в списке");
                    return;
                }

                // смотрим, можно ли еще принимать в игру игроков
                if (gmL[GameId].pl.Count >= 6)
                {
                    SendAnsv(27, 2, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (27) в игре уже максимальное количество игроков");
                    return;
                }

                // смотрим, есть ли такой бот
                Byte AIind = br.ReadByte(); // номер бота в dll AI
                String botName; // имя бота в dll AI
                try { botName = AI.GetBotName(AIind); }
                catch (Exception ex)
                {
                    SendAnsv(27, 3, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (27) " + ex.Message);
                    return;
                }

                // смотрим уникальное имя бота
                bool bUnic = false;
                int num = 0;
                while (!bUnic)
                {
                    bUnic = true;
                    for (int i = 0; i < gmL[GameId].pl.Count; i++)
                    {
                        if (gmL[GameId].pl[i].isBot && gmL[GameId].pl[i].Name.ToUpper().Trim() == botName.ToUpper() + (num == 0 ? "" : "(" + num.ToString() + ")"))
                        {
                            num++;
                            bUnic = false;
                            break;
                        }
                    }
                }

                // добавляем игрока в игру
                Player bot = new Player(botName + (num == 0 ? "" : "(" + num.ToString() + ")"), AIind);
                gmL[GameId].pl.Add(bot);
                AddLog("Добавляем бота '" + bot.Name + "' в игру");

                RefillGameList();

                // отправляем ответ
                SendAnsv(27, 0, GetMessGameList(cl[idCl].name), idCl);

                // посылаем обновленный список игр остальным юзерам
                SendGameListToAllUsers(idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (27) добавить бота: " + ex.Message); }
        }

        // ответ - положить карту на стол (50)
        void PlayCardInTable(byte[] buf, int idCl)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte();
                String gmName = br.ReadString(); // смотрим имя игры
                // если такого имени нет, отправляем ошибку
                if (gmName == "")
                {
                    SendAnsv(50, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (50) имя игры пустое");
                    return;
                }

                // находим игру
                int idGame = GetGameIdByName(gmName);
                if (idGame == -1)
                {
                    SendAnsv(50, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (50) не найдено имя игры в списке");
                    return;
                }

                // смотрим игрока с списке юзеров игры
                int idUser = GetUserInGameIdByName(idGame, cl[idCl].name);
                if (idGame == -1)
                {
                    SendAnsv(50, 2, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (50) не найден игрок в списке игры");
                    return;
                }

                Player pl = gmL[idGame].pl[idUser];

                // смотрим, может ли этот игрок сходиться
                if (gmL[idGame].PlayPl != idUser)
                {
                    SendAnsv(50, 6, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (50) этот игрок не может сходить, так как ход у игрока " + gmL[idGame].pl[gmL[idGame].PlayPl].Name);
                    return;
                }

                // смотрим номер карты в руке игрока
                int idCard = br.ReadByte();
                if (idCard < 0 || idCard >= pl.card.Count)
                {
                    SendAnsv(50, 3, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (50) не найдена карта в реку игрока");
                    return;
                }

                // смотрим номер города, куда следует поместить карту
                int idTown = br.ReadByte();
                if (idTown < 0 || idTown >= 5)
                {
                    SendAnsv(50, 4, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (50) не найден город № " + idTown.ToString());
                    return;
                }
                if (pl.town[idTown].card.Count >= 4)
                {
                    SendAnsv(50, 5, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (50) город уже заполнен");
                    return;
                }

                // кладем карту из руки в город
                PlayCard(gmL[idGame].pack, pl, idCard, idTown);
                // менаяем текущего игрока
                gmL[idGame].NextPl();

                // отправляем пакет-ответ
                SendAnsv(50, 0, GetMessUpdateTable(idGame, idUser), idCl);

                // отправляем обновленный стол остальным игрокам этой игры
                for (int i = 0; i < gmL[idGame].pl.Count; i++)
                {
                    if (i != idUser && !gmL[idGame].pl[i].isBot)
                    {
                        int idClInGame = Client.GetIdByName(cl, gmL[idGame].pl[i].Name);
                        SendAnsv(51, 0, GetMessUpdateTable(idGame, i), idClInGame);
                    }
                }

                // если следующий игрок - бот, ходим им
                if (gmL[idGame].pl[gmL[idGame].PlayPl].isBot)
                {
                    SetBotPlay(gmL[idGame], gmL[idGame].PlayPl);
                }
            }
            catch (Exception ex) { AddErr("ошибка ответа (50) положить карту на стол: " + ex.Message); }
        }

        /// <summary>
        /// выложить карту из руки на поле
        /// p - игрок, c - карта в руке, t - город, куда выкладывать
        /// новая карта раздается автоматически, если это возможно
        /// </summary>
        void PlayCard(List<Card> pack, Player pl, int c, int t)
        {
            // добавляем в город карту из руки 
            pl.town[t].card.Add(pl.card[c]);
            // удаляем карту из руки
            pl.card.RemoveAt(c);
            // выдаем новую карту, если это возможно
            if (pack.Count > 0)
            {
                pl.card.Add(pack[0]);
                pack.RemoveAt(0);
            }

            // выставляем координаты последней добавленой карты
            pl.LastCard.X = t;
            pl.LastCard.Y = pl.town[t].card.Count - 1;
            pl.cntTimeLastCard = 0;

        }

        // Ответ - сменить пароль (6)
        void ChangePass(byte[] buf, int idCl)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte(); // id - запроса
                String oldPass = br.ReadString(); // старый пароль
                String newPass = br.ReadString(); // новый пароль
                // смотрим, не пустое ли имя
                if (oldPass == "" || newPass == "")
                {
                    SendAnsv(6, 2, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (6) старый или новый пароль пустые");
                    return;
                }

                // смотрим, правильность старого пароля
                if (oldPass != cl[idCl].pass)
                {
                    SendAnsv(6, 1, null, idCl);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение клиенту " + cl[idCl].name + " (6) старый пароль не совпадает с текущим");
                    return;
                }

                // меняем пароль
                cl[idCl].pass = newPass;

                // сохраняем
                SaveClientList();

                SendAnsv(6, 0, null, idCl);
            }
            catch (Exception ex) { AddErr("ошибка ответа (6) сменить пароль: " + ex.Message); }
        }

        #region *** внутренние функции для ответов ***************************

        void PlayBot(int idGame)
        {
            PlayBot(gmL[idGame]);
        }

        class LastStepForTests
        {
            public static List<Town[]> EnemyTowns = new List<Town[]>();
            public static Town[] Towns = new Town[5];
            public static List<Card> Cards = new List<Card>();

            public static void SaveLastStep(List<Town[]> enemyTowns, Town[] towns, List<Card> cards)
            {
                EnemyTowns.Clear();
                for (int pl = 0; pl < enemyTowns.Count; pl++)
                {
                    EnemyTowns.Add(new Town[5]);
                    for (int t = 0; t < 5; t++) EnemyTowns[pl][t] = enemyTowns[pl][t].Clone();
                }

                for (int t = 0; t < 5; t++) Towns[t] = towns[t].Clone();

                Cards.Clear();
                for (int c = 0; c < cards.Count; c++) Cards.Add(cards[c].Clone());

            }
        }

        void PlayBot(Game game)
        {
            try
            {
                int idUser = game.PlayPl;
                //while (game.pl[idUser].isBot)
                {
                    // города противников
                    List<Town[]> EnemyTowns = new List<Town[]>();
                    for (int i = 0; i < game.pl.Count; i++) if (i != idUser) EnemyTowns.Add(game.pl[i].town);

                    // сохранить данные перед игрой карты (для тестов)
                    if (bTest) LastStepForTests.SaveLastStep(EnemyTowns, game.pl[idUser].town, game.pl[idUser].card);

                    // играть карту
                    CardPoint p = AI.PlayCard(EnemyTowns, game.pl[idUser].town, game.pl[idUser].card, game.pl[idUser].botInd);

                    // кладем карту из руки в город
                    PlayCard(game.pack, game.pl[idUser], p.NumCardInHand, p.NumTown);
                    // менаяем текущего игрока
                    game.NextPl();

                    // отправляем обновленный стол всем игрокам этой игры
                    for (int i = 0; i < game.pl.Count; i++)
                    {
                        if (!game.pl[i].isBot)
                        {
                            int idClInGame = Client.GetIdByName(cl, game.pl[i].Name);
                            SendAnsv(51, 0, GetMessUpdateTable(game, i), idClInGame);
                        }
                    }

                    // если следующий игрок тоже бот, возводим новое ожидание
                    if (game.pl[game.PlayPl].isBot) SetBotPlay(game, game.PlayPl);
                }
                DrawBotCardsInGame();
                if (bTest) ReFillAILog();
            }
            catch (Exception ex) { AddErr("ошибка игры ботом: " + ex.Message); }
        }

        int GetGameIdByName(String GameName)
        {
            for (int i = 0; i < gmL.Count; i++)
                if (gmL[i].Name == GameName)
                    return i;
            return -1;
        }

        int GetUserInGameIdByName(int GameId, String UserName)
        {
            for (int i = 0; i < gmL[GameId].pl.Count; i++)
                if (gmL[GameId].pl[i].Name == UserName)
                    return i;
            return -1;
        }

        #endregion *** внутренние функции для ответов ***************************

        #endregion *** посылка ответов на запросы ******************************************

        #region *** системные ответы ******************************************

        void AnsverNewVertion(int indClient)
        {
            BufWriter bw = new BufWriter();
            bw.WriteString("Новая");
            srv.Send(bw.buf, indClient);
        }

        void UpDateVersion(byte[] buf)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte();
                String s = br.ReadString();
                if (s == "Обновление")
                {
                    srv.Stop();
                    GC.Collect();
                    System.Diagnostics.Process.Start("NewVertion.exe");
                    Close();
                }
            }
            catch (Exception ex) { AddErr("ошибка обновления версии: " + ex.Message); }
        }

        void LogIn(byte[] buf, int indClient)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte();
                // смотрим имя
                String n = br.ReadString();
                String p = br.ReadString();

                int idCl = Client.GetIdByName(cl, n);

                // если такого логина нет, возвращаем ответ и выходим
                if (idCl == -1)
                {
                    SendAnsvUunknownCon(1, 1, null, indClient);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение неизвестному подключению (1) такого логина нет");
                    return;
                }
                // если такого пароля нет, возвращаем ответ и выходим
                if (cl[idCl].pass != p)
                {
                    SendAnsvUunknownCon(1, 2, null, indClient);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение неизвестному подключению (1) пароль неверен");
                    return;
                }

                // если такой юзер уже залогинен с другого адреса, возвращаем ответ и выходим
                if (cl[idCl].endPoint != null && !cl[idCl].bAdmin)
                {
                    SendAnsvUunknownCon(1, 3, null, indClient);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение неизвестному подключению (1) такой юзер уже залогинен");
                    return;
                }

                // запоминаем адрес клиента в списке клиентов сервера
                cl[idCl].endPoint = srv.Clients[indClient].RemoteEndPoint;
                BufWriter bw = new BufWriter();
                bw.WriteString(cl[idCl].name);
                AddLog("Отправляем удачный логин клиенту " + cl[idCl].name);
                SendAnsv(1, 0, bw.buf, idCl);

                if (n == "Администратор")
                {
                    Adm_SendClientList(idCl);
                    Adm_SendGameList(idCl);
                }
                else
                {
                    // отправляем всем обновленый список подключеных юзеров
                    SendClientList();
                    // оправить всем юзерам список обновленных игр (всем, чтобы показать, если этот юзер появился в сети)
                    SendGameListToAllUsers(-1);
                    // отправляем список доступных ботов подключенному юзеру
                    SendBotList(idCl);
                }
                // перезаполняем лист клиентов
                RefillClientList();
            }
            catch (Exception ex) { AddErr("ошибка логина: " + ex.Message); }
        }

        void Registration(byte[] buf, int indClient)
        {
            try
            {
                BufReader br = new BufReader(buf);
                br.ReadByte();
                // смотрим имя
                String n = br.ReadString();
                // смотрим пароль
                String p = br.ReadString();

                // если логин или пароль пустые возвращаем ответ
                if (n == "" || p == "")
                {
                    SendAnsvUunknownCon(2, 1, null, indClient);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение неизвестному подключению (2) логин или пароль пустые");
                    return;
                }

                // если имя - бот
                if (n.Substring(0, 3).ToUpper() == "БОТ")
                {
                    SendAnsvUunknownCon(2, 2, null, indClient);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение неизвестному подключению (2) бота регить нельзя");
                    return;
                }

                // если такого логина нет в списке
                if (!Client.DuplName(cl, n))
                {
                    // регистрируем нового юзера
                    cl.Add(new Client(n, p));
                    // перезаполняем лист клиентов
                    RefillClientList();
                    // сохраняем список
                    SaveClientList();
                }
                else
                {
                    SendAnsvUunknownCon(2, 2, null, indClient);
                    AddLog("ВНИМАНИЕ: Отправлено сообщение неизвестному подключению (2) такой пользователь уже зарегистрирован");
                    return;
                }

                AddLog("отправляем удачную регистрацию клиента " + cl[cl.Count - 1].name);
                SendAnsvUunknownCon(2, 0, null, indClient);
            }
            catch (Exception ex) { AddErr("ошибка регистрации: " + ex.Message); }
        }

        #endregion *** системные ******************************************

        // отправка пакета всем юзерам
        void SendPackToAllUsers(byte[] buf, int idClNotSet)
        {
            if (idClNotSet >= 0 && idClNotSet < cl.Count)
                AddLog("Добавляем в очередь отправки всем юзерам, кроме " + cl[idClNotSet].name + " пакет (" + buf[0].ToString() + ")");
            else
                AddLog("Добавляем в очередь отправки всем юзерам пакет (" + buf[0].ToString() + ")");
            for (int i = 0; i < cl.Count; i++)
            {
                if (i != idClNotSet && !cl[i].bAdmin)
                    AddSend(i, buf);
            }
        }

        // отправка ответа на какой-либо запрос
        void SendAnsv(byte b1, byte err, byte[] buf, int idCl)
        {
            AddLog("Отправлено ответ юзеру " + cl[idCl].name + " (" + b1.ToString() + ") err=" + err.ToString());
            BufWriter bw = new BufWriter();
            bw.WriteByte(b1);
            bw.WriteByte(err);
            if (buf != null) bw.AddBufWriterMas(buf);
            AddSend(idCl, bw.buf);
        }

        // отправка ответа неизвестному подключению
        void SendAnsvUunknownCon(byte b1, byte err, byte[] buf, int indClient)
        {
            BufWriter bw = new BufWriter();
            bw.WriteByte(b1);
            bw.WriteByte(err);
            if (buf != null) bw.AddBufWriterMas(buf);
            srv.Send(bw.buf, indClient);
        }

        // отправка сообщения от юзера всем в общий чат
        void SendClientMess(byte[] RecvBuf)
        {
            SendPackToAllUsers(RecvBuf, -1);
        }

        private void lbList_SelectedIndexChanged(object sender, EventArgs e)
        {
            //
        }

        private void button1_Click(object sender, EventArgs e)
        {
            if (lbList.SelectedIndex == -1) return;
            // если клиент подключен, дисконнектим его
            int ind = cl[lbList.SelectedIndex].GetInd(srv.Clients);
            if (ind != -1) srv.DisconnectClient(ind);
            cl.RemoveAt(lbList.SelectedIndex);
            RefillClientList();
            SaveClientList();
        }

        // все посылки стявятся очередь
        private void tmrRunSend_Tick(object sender, EventArgs e)
        {
            tmrRunSend.Stop();

            // проверка на ход бота
            foreach (Game game in gmL)
            {
                if (game.NumBotwHoWaitPlayCard != -1 && game.dtBotPlayCard <= DateTime.Now)
                {
                    game.NumBotwHoWaitPlayCard = -1;
                    PlayBot(game);
                }
            }

            for (int i = 0; i < cl.Count; i++)
            {
                try
                {
                    int n = 0;
                    // если есть, что отправлять, отправляем и сбрасываем счетчик ожидания
                    if (cl[i].sendL.Count > 0)
                    {
                        if (cl[i].endPoint == null)
                        {
                            int nErr = cl[i].sendL[0][0];
                            nErr *= -1;
                            cl[i].sendL.Clear();
                            n = nErr;
                        }

                        n = cl[i].sendL[0][0];
                        if (!SendPack(srv, cl[i], cl[i].sendL[0])) n *= -1;
                        cl[i].sendL.RemoveAt(0);
                        cl[i].cntSend = 0;
                    }
                    // иначе если уже 10 секунд ничего небыло отправлено, отправляем проверочный пакет
                    // и сбрасываем счетчик ожидания
                    else if (cl[i].cntSend >= 200)
                    {
                        byte[] buf = new byte[1] { 10 };
                        if (!SendPack(srv, cl[i], buf)) n *= -1;
                        cl[i].cntSend = 0;
                        n = -10001;
                    }

                    if (n > 0) AddLog("ОТПРАВЛЕНО (" + n.ToString() + ") клиенту " + cl[i].name);
                    if (n > -10000 && n < 0) AddErr("Не отправлено (" + Math.Abs(n).ToString() + ") клиенту " + cl[i].name);

                    cl[i].cntSend++;
                }
                catch (Exception ex)
                {
                    AddLog("ОШИБКА В ПРИЛОЖЕНИИ: при попытке отправки сообщения клиенту " + cl[i].name + " (" + cl[i].endPoint.ToString() + "). ТЕКСТ ОШИБКИ: " + ex.Message + " ТРАССИРОВКА: " + ex.StackTrace);
                }
            }
            tmrRunSend.Start();
        }

        bool SendPack(ETcpServer srv, Client clnt, byte[] buf)
        {
            int ind = clnt.GetInd(srv.Clients);
            if (ind == -1) return false;
            if (srv.Send(buf, ind) != SendAns.Ok) return false;
            return true;
        }

        /// <summary>отправка проверочных пакетов неизвестным соединениям и проверка игр на удаление</summary>
        private void tmrSend_Tick(object sender, EventArgs e)
        {
            // отправка проверочных пакетов неизвестным соединениям
            byte[] buf = new byte[1] { 10 };
            for (int i = 0; i < srv.Clients.Count; i++)
            {
                if (Client.GetIdByEndPoint(cl, srv.Clients[i].RemoteEndPoint) == -1)
                {
                    srv.Send(buf, i);
                }
            }

            // проверяем игры на удаление
            for (int i = 0; i < gmL.Count; i++)
            {
                // если игра запущена 
                if (gmL[i].GameIsPlay)
                {
                    // если все юзеры offline, прибавляем счетчик удаления
                    bool bOff = true;
                    for (int j = 0; j < gmL[i].pl.Count; j++)
                    {
                        int idCl = Client.GetIdByName(cl, gmL[i].pl[j].Name);
                        if (idCl != -1)
                        {
                            if (cl[idCl].endPoint != null)
                            {
                                bOff = false;
                                break;
                            }
                        }
                    }
                    if (bOff) gmL[i].cntDelete++;
                    else gmL[i].cntDelete = 0;
                }
                else
                {
                    // проверяем жив ли модератор
                    int idCl = Client.GetIdByName(cl, gmL[i].Moderator);
                    if (cl[idCl].endPoint == null) gmL[i].cntDelete++;
                    else gmL[i].cntDelete = 0;
                }

                // если счетчик удаления какой-либо игры > 150 (уже полчаса висит)
                // удаляем ее
                if (gmL[i].cntDelete > 150)
                {
                    gmL.RemoveAt(i);
                    i--;
                    // отправляем остальным юзерам обновленный список игр
                    SendGameListToAllUsers(-1);
                    RefillGameList();
                }
            }

        }

        private void chbxVisLog_CheckedChanged(object sender, EventArgs e)
        {
            RefillLog();
        }

        #region *** отрисовка карт ботов ****************************************************

        void DrawBotCardsInGame()
        {
            Game game = null;
            if (tvGames.SelectedNode != null) game = (Game)tvGames.SelectedNode.Tag;

            // подсчет ботов для отрисовки
            List<Player> bots = new List<Player>();
            if (game != null && game.GameIsPlay)
            {
                foreach (Player plr in game.pl) if (plr.isBot) bots.Add(plr);
            }

            if (im.Image != null) im.Image.Dispose();

            if (bots.Count == 0)
            {
                im.Image = new Bitmap(1, 1);
                im.Refresh();
            }
            else
            {
                im.Image = new Bitmap(bots.Count * 110 + 20, 300);
                Graphics gr = Graphics.FromImage(im.Image);
                gr.Clear(Color.White);
                for (int i = 0; i < bots.Count; i++)
                {
                    DrawBotHand(gr, bots[i], i * 110 + 10);
                }
                im.Refresh();
            }

        }

        void ReFillAILog()
        {
            txbAiLog.Clear();
            StringBuilder sb = new StringBuilder();
            foreach (string s in AI.logL) sb.AppendLine(s);
            txbAiLog.Text = sb.ToString();
            txbAiLog.Select(txbAiLog.Text.Length, 0);
            txbAiLog.ScrollToCaret();
        }

        int stwpY = 20;
        void DrawBotHand(Graphics gr, Player pl, int shX)
        {
            gr.DrawString(pl.Name, Font, Brushes.Black, new Rectangle(shX, 10, 100, 15));
            for (int i = 0; i < pl.card.Count; i++)
                gr.DrawImage(bmpCard[pl.card[i].num], shX, stwpY * i + 25);
        }

        #endregion *** отрисовка карт ботов ****************************************************

        private void tvGames_AfterSelect(object sender, TreeViewEventArgs e)
        {
            DrawBotCardsInGame();
        }

        #region *** ТЕСТ *********************************************************************

        // позиции квадратиков картинок (для нажатия)
        Dictionary<int, Rectangle> PosRect = new Dictionary<int, Rectangle>();
        Dictionary<int, Rectangle> PosRectC = new Dictionary<int, Rectangle>();

        List<Town[]> tt_EnTowns = new List<Town[]>();
        Town[] tt_Town = new Town[5];
        List<Card> tt_Cards = new List<Card>();
        CardPoint tt_cp = new CardPoint(-1, -1);

        Brush tt_br1 = Brushes.Gray;

        private void btnTestBegin_Click(object sender, EventArgs e)
        {
            tt_cp = new CardPoint(-1, -1);
            tt_EnTowns.Clear();
            Town[] towns = new Town[5]; for (int t = 0; t < 5; t++) towns[t] = new Town();
            tt_EnTowns.Add(towns);
            towns = new Town[5]; for (int t = 0; t < 5; t++) towns[t] = new Town();
            tt_EnTowns.Add(towns);

            for (int t = 0; t < 5; t++) tt_Town[t] = new Town();

            tt_Cards.Clear();
            for (int i = 0; i < 5; i++) tt_Cards.Add(new Card(false, false, false, false, false));
            SaveTest(Application.StartupPath + "\\Test.Save");
            RefillTestFld();
        }

        private void btnTestPlayCard_Click(object sender, EventArgs e)
        {
            List<Town[]> enTowns = new List<Town[]>();
            enTowns.Add(tt_EnTowns[0]);
            if (chbxTestEnemy2.Checked) enTowns.Add(tt_EnTowns[1]);

            tt_cp = AI.PlayCard(enTowns, tt_Town, tt_Cards, 2);
            RefillTestFld();
        }

        private void btnTestLoad_Click(object sender, EventArgs e)
        {
            tt_cp = new CardPoint(-1, -1);
            LoadTest(Application.StartupPath + "\\Test.Save");
            RefillTestFld();
        }

        void RefillTestFld()
        {
            PosRect.Clear();

            if (imTestFld.Image != null) imTestFld.Image.Dispose();
            imTestFld.Image = new Bitmap(imTestFld.Width, imTestFld.Height);
            Graphics gr = Graphics.FromImage(imTestFld.Image);
            gr.Clear(Color.White);

            gr.DrawImage(new Bitmap(new Bitmap(Application.StartupPath + "\\Data\\Town.png"), new Size(120, 19)), 50, 50);
            // карты в городах
            DrawTowns(gr, tt_Town, 0);

            // выкладываемая карта
            if (tt_cp.NumTown != -1)
            {
                int x = 10 + tt_cp.NumTown * 40;
                int y = 70 + tt_Town[tt_cp.NumTown].card.Count * 12;

                for (int tr = 0; tr < 5; tr++)
                {
                    int x1 = 2 + x + tr * 7;
                    if (tt_Cards[tt_cp.NumCardInHand].troop[tr]) gr.FillRectangle(Brushes.Green, x1, y + 2, 7, 7);
                    gr.DrawRectangle(Pens.Black, x1, y + 2, 7, 7);

                }
            }

            // города противников
            for (int i = 0; i < tt_EnTowns.Count; i++)
            {
                int x = 300 + i * 250;
                gr.DrawImage(new Bitmap(new Bitmap(Application.StartupPath + "\\Data\\Town.png"), new Size(120, 19)), x, 50);
                // карты в городах
                DrawTowns(gr, tt_EnTowns[i], i + 1);
            }

            gr.Dispose();
            imTestFld.Refresh();

            RefillTestHand();

            RefillTestLog();
        }

        void RefillTestHand()
        {
            PosRectC.Clear();

            if (imTestCard.Image != null) imTestCard.Image.Dispose();
            imTestCard.Image = new Bitmap(imTestCard.Width, imTestCard.Height);
            Graphics gr = Graphics.FromImage(imTestCard.Image);
            gr.Clear(Color.White);

            for (int c = 0; c < tt_Cards.Count; c++)
            {
                int y = 10 + c * 20;
                gr.FillRectangle(tt_cp.NumCardInHand == c ? Brushes.YellowGreen : Brushes.Wheat, 40, y, 55, 80);
                gr.DrawRectangle(Pens.Black, 40, y, 55, 80);

                for (int tr = 0; tr < 5; tr++)
                {
                    int x1 = 43 + tr * 10;
                    if (tt_Cards[c].troop[tr]) gr.FillRectangle(tt_br1, x1, y + 2, 8, 8);
                    gr.DrawRectangle(Pens.Black, x1, y + 2, 8, 8);

                    PosRectC.Add(c * 10 + tr, new Rectangle(x1, y + 2, 8, 8));
                }
            }

            gr.Dispose();
            imTestCard.Refresh();
        }

        void RefillTestLog()
        {
            txbTestLog.Clear();
            StringBuilder sb = new StringBuilder();
            foreach (string s in AI.logL) sb.AppendLine(s);
            txbTestLog.Text = sb.ToString();
            txbTestLog.Select(sb.Length, 0);
            txbTestLog.ScrollToCaret();
        }

        void DrawTowns(Graphics gr, Town[] Towns, int plInd)
        {
            for (int t = 0; t < 5; t++)
            {
                int x = 10 + t * 40 + plInd * 250;
                for (int c = 0; c < 4; c++)
                {
                    bool bCard = c < Towns[t].card.Count;

                    int y = 70 + c * 12;
                    gr.FillRectangle(bCard ? Brushes.Wheat : Brushes.White, x + 1, y, 38, 50);
                    gr.DrawRectangle(bCard ? Pens.Black : Pens.Gray, x + 1, y, 38, 50);

                    for (int tr = 0; tr < 5; tr++)
                    {
                        int x1 = 2 + x + tr * 7;
                        gr.DrawRectangle(Pens.Gray, x1, y + 2, 7, 7);
                        if (bCard)
                        {
                            if (Towns[t].card[c].troop[tr]) gr.FillRectangle(tt_br1, x1, y + 2, 7, 7);
                            gr.DrawRectangle(Pens.Black, x1, y + 2, 7, 7);
                        }
                        PosRect.Add(plInd * 1000 + t * 100 + c * 10 + tr, new Rectangle(x1, y + 2, 7, 7));
                    }
                }
            }
        }

        private void imTestFld_MouseDown(object sender, MouseEventArgs e)
        {
            // нахождение позиции клика
            int n = -1;
            foreach (KeyValuePair<int, Rectangle> val in PosRect)
            {
                if (PointInRect(val.Value, e.Location)) { n = val.Key; break; }
            }
            if (n == -1) return;

            tt_cp.NumTown = -1;

            int pl = n / 1000;
            int t = (n % 1000) / 100;
            int c = (n % 100) / 10;
            int tr = n % 10;

            // игрок
            if (pl == 0)
            {
                ResetTown(tt_Town[t], c, tr);
            }
            // противник
            else
            {
                ResetTown(tt_EnTowns[pl - 1][t], c, tr);
            }

            RefillTestFld();
            SaveTest(Application.StartupPath + "\\Test.Save");
        }

        private void imTestCard_MouseDown(object sender, MouseEventArgs e)
        {
            // нахождение позиции клика
            int n = -1;
            foreach (KeyValuePair<int, Rectangle> val in PosRectC)
            {
                if (PointInRect(val.Value, e.Location)) { n = val.Key; break; }
            }
            if (n == -1) return;

            tt_cp.NumTown = -1;

            int c = n / 10;
            int tr = n % 10;

            tt_Cards[c].troop[tr] = !tt_Cards[c].troop[tr];
            RefillTestHand();
            SaveTest(Application.StartupPath + "\\Test.Save");
        }

        void ResetTown(Town town, int c, int tr)
        {
            if (c > town.card.Count) return;
            if (c == town.card.Count) town.card.Add(new Card(false, false, false, false, false));

            town.card[c].troop[tr] = !town.card[c].troop[tr];

            // проверка на удаление
            bool bDel = true;
            while (bDel && town.card.Count > 0)
            {
                foreach (bool bTr in town.card[town.card.Count - 1].troop) if (bTr) { bDel = false; break; }
                if (bDel) town.card.RemoveAt(town.card.Count - 1);
            }
        }

        /// <summary> точка в прямоугольнике </summary>
        public static bool PointInRect(Rectangle r, Point p)
        {
            if (p.Y >= r.Top & p.Y < r.Bottom & p.X >= r.Left & p.X < r.Right)
                return true;
            if (p.Y < r.Top & p.Y >= r.Bottom & p.X < r.Left - 1 & p.X >= r.Right)
                return true;
            return false;
        }

        void SaveTest(string fPath)
        {
            EStream st = new EStream(fPath);
            st.OpenSave();

            // города противников
            st.SaveInt(tt_EnTowns.Count);
            foreach (Town[] towns in tt_EnTowns)
                SaveTowns(st, towns);

            // города игрока
            SaveTowns(st, tt_Town);

            // карты в руке игрока
            st.SaveInt(tt_Cards.Count);
            foreach (Card card in tt_Cards)
                SaveCard(st, card);

            st.CloseSave();
        }

        void SaveTowns(EStream st, Town[] towns)
        {
            foreach (Town town in towns)
            {
                st.SaveInt(town.card.Count);
                for (int c = 0; c < town.card.Count; c++)
                    SaveCard(st, town.card[c]);
            }
        }

        void SaveCard(EStream st, Card card)
        {
            foreach (bool tr in card.troop)
                st.SaveBool(tr);
        }

        void LoadTest(string fPath)
        {
            if (!File.Exists(fPath)) { btnTestBegin_Click(null, null); return; }

            EStream st = new EStream(fPath);
            st.OpenLoad();

            // города противников
            tt_EnTowns.Clear();
            int kolVo = st.LoadInt();
            for (int i = 0; i < kolVo; i++)
            {
                tt_EnTowns.Add(new Town[5]);
                LoadTowns(st, tt_EnTowns[tt_EnTowns.Count - 1]);
            }

            // города игрока
            LoadTowns(st, tt_Town);

            // карты в руке игрока
            tt_Cards.Clear();
            kolVo = st.LoadInt();
            for (int i = 0; i < kolVo; i++)
            {
                tt_Cards.Add(LoadCard(st));
            }

            st.CloseLoad();
        }

        void LoadTowns(EStream st, Town[] towns)
        {
            for (int t = 0; t < 5; t++)
            {
                towns[t] = new Town();
                int kolVo = st.LoadInt();
                for (int c = 0; c < kolVo; c++)
                    towns[t].card.Add(LoadCard(st));
            }
        }

        Card LoadCard(EStream st)
        {
            return new Card(st.LoadBool(), st.LoadBool(), st.LoadBool(), st.LoadBool(), st.LoadBool());
        }

        private void btnLoadInTest_Click(object sender, EventArgs e)
        {
            tt_EnTowns.Clear();

            Town[] towns = new Town[5]; for (int t = 0; t < 5; t++) towns[t] = new Town();
            tt_EnTowns.Add(towns);
            towns = new Town[5]; for (int t = 0; t < 5; t++) towns[t] = new Town();
            tt_EnTowns.Add(towns);

            for (int pl = 0; pl < tt_EnTowns.Count && pl < LastStepForTests.EnemyTowns.Count; pl++)
            {
                for (int t = 0; t < 5; t++)
                    tt_EnTowns[pl][t] = LastStepForTests.EnemyTowns[pl][t].Clone();
            }

            for (int t = 0; t < 5; t++) tt_Town[t] = LastStepForTests.Towns[t].Clone();

            tt_Cards.Clear();
            for (int i = 0; i < LastStepForTests.Cards.Count; i++) tt_Cards.Add(LastStepForTests.Cards[i]);
            SaveTest(Application.StartupPath + "\\Test.Save");
            RefillTestFld();
        }

        #endregion *** ТЕСТ *********************************************************************

        private void btnTestPlay_Click(object sender, EventArgs e)
        {
            int cntGames = 100;
            int cntWin1 = 0;
            int cntWin2 = 0;
            int cntDrawnGame = 0;

            progressBar1.Visible = true;
            progressBar1.Value = 0;
            progressBar1.Maximum = cntGames;
            btnTestPlay.Enabled = false;

            for (int i = 0; i < cntGames; i++)
            {
                List<Player> pl = new List<Player>() { new Player("игрок1", 0), new Player("игрок1", 2) };
                List<Card> pack = Gl.CreatePack(2, 1, 1, 2, 3);
                Gl.Shuffle(pack);
                Gl.DealCards(pl, pack);

                bool bGameOver = false;
                int actPlayer = 0;
                int enPlayer = 1;

                while (!bGameOver)
                {
                    // проверка на окончание игры
                    bGameOver = true;
                    for (int p = 0; p < pl.Count; p++)
                    {
                        for (int t = 0; t < 5; t++)
                            if (pl[p].town[t].card.Count < 4)
                            {
                                bGameOver = false;
                                break;
                            }
                        if (!bGameOver) break;
                    }

                    if (bGameOver)
                    {
                        int cnt1 = 0;
                        int cnt2 = 0;
                        for (int t = 0; t < 5; t++)
                        {
                            Town.LeaderStat stat = pl[0].town[t].Leader(pl[1].town[t]);
                            if (stat == Town.LeaderStat.Win) cnt1++;
                            else if (stat == Town.LeaderStat.Equal) { cnt1++; cnt2++; }
                            else cnt2++;
                        }

                        // подсчет
                        if (cnt1 > cnt2) cntWin1++;
                        if (cnt2 > cnt1) cntWin2++;
                        if (cnt1 == cnt2) cntDrawnGame++;

                        break;

                    }

                    CardPoint pnt = AI.PlayCard(new List<Town[]>() { pl[enPlayer].town }, pl[actPlayer].town, pl[actPlayer].card, pl[actPlayer].botInd);

                    // кладем карту из руки в город
                    PlayCard(pack, pl[actPlayer], pnt.NumCardInHand, pnt.NumTown);

                    int c = actPlayer;
                    actPlayer = enPlayer;
                    enPlayer = c;

                    progressBar1.Value = i;

                    lblTestBots1.Text = AI.GetBotName((byte)pl[0].botInd) + " выиграл: " + cntWin1.ToString();
                    lblTestBots2.Text = AI.GetBotName((byte)pl[1].botInd) + " выиграл: " + cntWin2.ToString();
                    lblTestBots3.Text = "ничья: " + cntDrawnGame.ToString();
                    lblTestBots4.Text = "всего сыграно: " + (i + 1).ToString();
                    Application.DoEvents();
                    System.Threading.Thread.Sleep(1);
                }
            }

            progressBar1.Visible = false;
            btnTestPlay.Enabled = true;

        }

        private void label3_Click(object sender, EventArgs e)
        {

        }

    }
}
